﻿using System;
using System.Collections.Generic;
using System.Linq;

using btmerc.API.Enums;
using btmerc.API.Schemas;
using btmerc.API.Schemas.Interfaces;

namespace btmerc.API
{
    public class Employer
    {
        public Affiliation Affiliation { get; set; }
        public EmployerType EmployerType { get; set; }
    }

    public class ContractGenerator
    {
        IEra _era;
        IGeneralContractModifiers _modifiers;
        IGeneralContractTermAdjustments _termAdjustments;

        public ContractGenerator(IEra era, IGeneralContractModifiers modifiers, IGeneralContractTermAdjustments termAdjustments)
        {
            _era = era;
            _modifiers = modifiers;
            _termAdjustments = termAdjustments;
        }

        public IEnumerable<Contract> GenerateContracts(Force force, string hall)
        {
            HiringHall hiringHall = _era.HiringHall.Where(h => h.Name == hall).FirstOrDefault();
            DragoonsRating ratingScheme = new DragoonsRating(force);
            TroopType type = force.GetMainForceType();
            Rating rating = ratingScheme.Rating;
            if (hiringHall == null)
            {
                throw new ArgumentException("This hiring hall does not exist.");
            }
            short offerRoll = (short)(Die.Instance.RollDice(2) + GetOfferModifiers(type, rating, hiringHall));
            ushort totalOffers = GetOfferCount(offerRoll);
            List<Contract> contractList = new List<Contract>();
            for (byte i = 0; i < totalOffers; i++)
            {
                contractList.Add(GenerateContract(type, rating, hiringHall));
            }

            return contractList;
        }

        #region Private Helper Functions
        private Contract GenerateContract(TroopType type, Rating rating, HiringHall hiringHall)
        {
            byte employerRoll = Die.Instance.RollDice(3);
            short employerModifier = GetEmployerModifiers(type, rating, hiringHall);
            Employer employer = GetEmployer(employerRoll, employerModifier);

            byte contractRoll = Die.Instance.RollDice(2);
            short contractModifier = GetContractTypeModifiers(type, rating, hiringHall);
            bool isCovert;
            ContractType contractType = GetContractType(contractRoll, contractModifier, employer.EmployerType, out isCovert);
            ContractTerms contractTerms = GetContractTerms(contractType, employer, rating, isCovert);

            return new Contract
            {
                Employer = employer.Affiliation,
                ContractType = contractType,
                ContractTerms = contractTerms
            };
        }

        private short GetOfferModifiers(TroopType type, Rating dragoonsRating, HiringHall hiringHall)
        {
            return (short) (_era.ContractAdjustments.ContractModifiers.OffersModifier + 
                            _modifiers.GetOfferModifier(type, dragoonsRating, hiringHall));
        }

        private byte GetOfferCount(short modifiedRoll)
        {
            if (modifiedRoll < 4)
            {
                return 0;
            }
            if (modifiedRoll < 6)
            {
                return 1;
            }
            if (modifiedRoll < 10)
            {
                return 2;
            }
            if (modifiedRoll < 13)
            {
                return 3;
            }
            if (modifiedRoll < 16)
            {
                return 4;
            }
            else
            {
                return 5;
            }
        }

        private short GetEmployerModifiers(TroopType type, Rating dragoonsRating, HiringHall hiringHall)
        {
            return (short)(_era.ContractAdjustments.ContractModifiers.EmployersModifier + 
                           _modifiers.GetEmployerModifier(type, dragoonsRating, hiringHall));
        }

        private Employer GetEmployer(byte dieRoll, short modifiers)
        {
            short modifiedDieRoll = (short)(dieRoll + modifiers);
            if (modifiedDieRoll < 4)
            {
                return new Employer
                {
                    EmployerType = EmployerType.Periphery,
                    Affiliation = Affiliation.IndependentWorld
                };
            }
            if (modifiedDieRoll < 5)
            {
                short newRoll = (short)(Die.Instance.RollDice(3) + modifiers);
                return GetPeripheryEmployer(newRoll);
            }
            if (modifiedDieRoll == 6)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.ChaosMarch
                };
            }
            if (modifiedDieRoll == 7)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.FreeWorldsLeague
                };
            }
            if (modifiedDieRoll == 8)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.WordofBlake
                };
            }
            if (modifiedDieRoll == 9)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.CapellanConfederation
                };
            }
            if (modifiedDieRoll == 10)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.FederatedSuns
                };
            }
            if (modifiedDieRoll == 11)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.LyranCommonwealth
                };
            }
            if (modifiedDieRoll == 12)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.DraconisCombine
                };
            }
            if (modifiedDieRoll == 13)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.ComStar
                };
            }
            if (modifiedDieRoll == 14)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.SLDF
                };
            }
            if (modifiedDieRoll == 15)
            {
                return new Employer
                {
                    EmployerType = EmployerType.InnerSphere,
                    Affiliation = Affiliation.FreeRasalhagueRepublic
                };
            }
            if (modifiedDieRoll == 16)
            {
                short newRoll = (short)(Die.Instance.RollDice(3) + modifiers);
                return new Employer
                {
                    EmployerType = EmployerType.Mercenary,
                    Affiliation = Affiliation.Mercenary1
                };

                //return GetMercenaryEmployer(newRoll);
            }
            else
            {
                short newRoll = (short)(Die.Instance.RollDice(3) + modifiers);
                return new Employer
                {
                    EmployerType = EmployerType.Corporation,
                    Affiliation = Affiliation.Corporation1
                };
                //return GetCorporateEmployer(newRoll);
            }
        }

        private Employer GetPeripheryEmployer(short modifiedDieRoll)
        {
            Employer employer = new Employer();
            employer.EmployerType = EmployerType.Periphery;
            employer.Affiliation = Affiliation.Periphery;
            /*if (modifiedDieRoll < 4)
            {
                employer.Name = "Nueva Castile";
            }
            if (modifiedDieRoll == 4)
            {
                employer.Name = "Umayyad Caliphate";
            }
            if (modifiedDieRoll == 5)
            {
                employer.Name = "Astrokaszy";
            }
            if (modifiedDieRoll == 6)
            {
                employer.Name = "Antallos (Port Krin)";
            }
            if (modifiedDieRoll == 7)
            {
                employer.Name = "Tortuga Dominions";
            }
            if (modifiedDieRoll == 8)
            {
                employer.Name = "Mica Majority";
            }
            if (modifiedDieRoll == 9)
            {
                employer.Name = "Circinus Federation";
            }
            if (modifiedDieRoll == 10)
            {
                employer.Name = "Calderon Protectorate";
            }
            if (modifiedDieRoll == 11)
            {
                employer.Name = "Marian Hegemony";
            }
            if (modifiedDieRoll == 12)
            {
                employer.Name = "Magistracy of Canopus";
            }
            if (modifiedDieRoll == 13)
            {
                employer.Name = "Taurian Concordat";
            }
            if (modifiedDieRoll == 14)
            {
                employer.Name = "Fronc Reaches";
            }
            if (modifiedDieRoll == 15)
            {
                employer.Name = "Rim Collection";
            }
            if (modifiedDieRoll == 16)
            {
                employer.Name = "Nios Association";
            }
            if (modifiedDieRoll == 17)
            {
                employer.Name = "Franklin Fiefs";
            }
            else
            {
                employer.Name = "Fiefdom of Randis";
            }*/

            return employer;
        }

        private Employer GetMercenaryEmployer(short modifiedDieRoll)
        {
            // TODO: add logic for different Mercenary Companies
            return new Employer
            {
                EmployerType = EmployerType.Mercenary,
                Affiliation = Affiliation.Mercenary1
            };
        }

        private Employer GetCorporateEmployer(short modifiedDieRoll)
        {
            // TODO: add logic for different corporations
            return new Employer
            {
                EmployerType = EmployerType.Corporation,
                Affiliation = Affiliation.Corporation1
            };
        }

        private short GetContractTypeModifiers(TroopType type, Rating dragoonsRating, HiringHall hiringHall)
        {
            return (short)(_era.ContractAdjustments.ContractModifiers.MissionsModifier + 
                           _modifiers.GetMissionModifier(type, dragoonsRating, hiringHall));
        }

        private ContractType GetContractType(byte dieRoll, short totalModifiers, EmployerType employerType, out bool isCovert)
        {
            isCovert = false;
            switch (employerType)
            {
                case EmployerType.InnerSphere:
                    return GetInnerSphereContractType(dieRoll, totalModifiers, out isCovert);
                case EmployerType.Periphery:
                    return GetPeripheryContractType(dieRoll, totalModifiers, out isCovert);
                case EmployerType.Corporation:
                    return GetCorporationContractType(dieRoll, totalModifiers, out isCovert);
                default:
                    throw new ArgumentOutOfRangeException("Unexpected EmployerType");
            }
        }

        private ContractType GetInnerSphereContractType(byte dieRoll, short totalModifiers, out bool isCovert)
        {
            isCovert = false;
            short modifiedDieRoll = (short)(dieRoll + totalModifiers);
            if (modifiedDieRoll < 3)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetCovertContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 3)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetSpecialContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 4)
            {
                return ContractType.PirateHunting;
            }
            if (modifiedDieRoll == 5)
            {
                return ContractType.PlanetaryAssault;
            }
            if (modifiedDieRoll == 6)
            {
                return ContractType.RiotDuty;
            }
            if (modifiedDieRoll == 7)
            {
                return ContractType.ObjectiveRaid;
            }
            if (modifiedDieRoll == 8)
            {
                return ContractType.ExtractionRaid;
            }
            if (modifiedDieRoll == 9)
            {
                return ContractType.ReconRaid;
            }
            if (modifiedDieRoll == 10)
            {
                return ContractType.GarrisonDuty;
            }
            if (modifiedDieRoll == 11)
            {
                return ContractType.CadreDuty;
            }
            else
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetSpecialContractType(newDieRoll, totalModifiers, out isCovert);
            }
        }

        private ContractType GetPeripheryContractType(byte dieRoll, short totalModifiers, out bool isCovert)
        {
            isCovert = false;
            short modifiedDieRoll = (short)(dieRoll + totalModifiers);
            if (modifiedDieRoll < 3)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetCovertContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 3)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetSpecialContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 4)
            {
                return ContractType.PlanetaryAssault;
            }
            if (modifiedDieRoll == 5)
            {
                return ContractType.RiotDuty;
            }
            if (modifiedDieRoll == 6)
            {
                return ContractType.ExtractionRaid;
            }
            if (modifiedDieRoll == 7)
            {
                return ContractType.PirateHunting;
            }
            if (modifiedDieRoll == 8)
            {
                return ContractType.SecurityDuty;
            }
            if (modifiedDieRoll == 9)
            {
                return ContractType.ObjectiveRaid;
            }
            if (modifiedDieRoll == 10)
            {
                return ContractType.GarrisonDuty;
            }
            if (modifiedDieRoll == 11)
            {
                return ContractType.CadreDuty;
            }
            else
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetSpecialContractType(newDieRoll, totalModifiers, out isCovert);
            }
        }

        private ContractType GetCorporationContractType(byte dieRoll, short totalModifiers, out bool isCovert)
        {
            isCovert = false;
            short modifiedDieRoll = (short)(dieRoll + totalModifiers);
            if (modifiedDieRoll < 3)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetCovertContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 3)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetCovertContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 4)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetSpecialContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 5)
            {
                return ContractType.ObjectiveRaid;
            }
            if (modifiedDieRoll == 6)
            {
                return ContractType.ExtractionRaid;
            }
            if (modifiedDieRoll == 7)
            {
                return ContractType.ReconRaid;
            }
            if (modifiedDieRoll == 8)
            {
                return ContractType.ObjectiveRaid;
            }
            if (modifiedDieRoll == 9)
            {
                return ContractType.SecurityDuty;
            }
            if (modifiedDieRoll == 10)
            {
                return ContractType.GarrisonDuty;
            }
            if (modifiedDieRoll == 11)
            {
                return ContractType.CadreDuty;
            }
            else
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetSpecialContractType(newDieRoll, totalModifiers, out isCovert);
            }
        }

        private ContractType GetSpecialContractType(byte dieRoll, short totalModifiers, out bool isCovert)
        {
            isCovert = false;
            short modifiedDieRoll = (short)(dieRoll + totalModifiers);
            if (modifiedDieRoll < 3)
            {
                byte newDieRoll = Die.Instance.RollDice(2);
                return GetCovertContractType(newDieRoll, totalModifiers, out isCovert);
            }
            if (modifiedDieRoll == 3)
            {
                throw new NotImplementedException();
            }
            if (modifiedDieRoll == 4)
            {
                return ContractType.Guerrilla;
            }
            if (modifiedDieRoll == 5)
            {
                throw new NotImplementedException();
            }
            if (modifiedDieRoll == 6)
            {
                return ContractType.ExtractionRaid;
            }
            if (modifiedDieRoll == 7)
            {
                return ContractType.Retainer;
            }
            if (modifiedDieRoll == 8)
            {
                return ContractType.ReconRaid;
            }
            if (modifiedDieRoll == 9)
            {
                return ContractType.ReliefDuty;
            }
            if (modifiedDieRoll == 10)
            {
                throw new NotImplementedException();
            }
            if (modifiedDieRoll == 11)
            {
                return ContractType.RiotDuty;
            }
            else
            {
                return ContractType.CadreDuty;
            }
        }

        private ContractType GetCovertContractType(byte dieRoll, short totalModifiers, out bool isCovert)
        {
            isCovert = true;
            short modifiedDieRoll = (short)(dieRoll + totalModifiers);
            if (modifiedDieRoll < 3)
            {
                return ContractType.Terrorism;
            }
            if (modifiedDieRoll == 3)
            {
                return ContractType.Assassination;
            }
            if (modifiedDieRoll == 4)
            {
                return ContractType.Assassination;
            }
            if (modifiedDieRoll == 5)
            {
                return ContractType.Espionage;
            }
            if (modifiedDieRoll == 6)
            {
                return ContractType.Sabotage;
            }
            if (modifiedDieRoll == 7)
            {
                return ContractType.Guerrilla;
            }
            if (modifiedDieRoll == 8)
            {
                return ContractType.ReconRaid;
            }
            if (modifiedDieRoll == 9)
            {
                return ContractType.Diversion;
            }
            if (modifiedDieRoll == 10)
            {
                return ContractType.Observation;
            }
            if (modifiedDieRoll == 11)
            {
                return ContractType.MoleHunting;
            }
            else
            {
                return ContractType.SecurityDuty;
            }
        }

        private ContractTerms GetContractTerms(ContractType contractType, Employer employer, Rating rating, bool isCovert)
        {
            SalvageRightsType salvageType;
            float chartered, owned, straight, battle;
            byte length = GetDuration(contractType, employer, rating);
            float paymentMultiplier = GetPaymentMultiplier(contractType, employer, rating, isCovert);
            short salvagePercentage = GetSalvageRights(contractType, employer, rating, isCovert, out salvageType);
            GetSupportRights(contractType, employer, rating, isCovert, out straight, out battle);
            GetTransportRights(contractType, employer, rating, isCovert, out chartered, out owned);
            ContractTerms contractTerms = new ContractTerms
            {
                PaymentMultiplier = paymentMultiplier,
                Duration = length,
                CommandRights = GetCommandRights(contractType, employer, rating, isCovert),
                OverheadCompensation = GetOverheadCompensation(contractType, employer, rating, isCovert),
                SalvagePercentage = salvagePercentage,
                SalvageRightsType = salvageType,
                StraightSupportPercentage = straight,
                BattleSupportPercentage = battle,
                TransportCompensationChartered = chartered,
                TransportCompensationOwned = owned,
            };

            return contractTerms;
        }

        private float GetPaymentMultiplier(ContractType contractType, Employer employer, Rating rating, bool isCovert)
        {
            return (float)(_termAdjustments.GetPaymentMultiplier(contractType, employer, rating, isCovert) *
                          _era.ContractAdjustments.ContractTermAdjustments.PaymentMultiplier);
        }

        private byte GetDuration(ContractType contractType, Employer employer, Rating rating)
        {
            return (byte)(_termAdjustments.GetDuration(contractType, employer, rating) +
                          _era.ContractAdjustments.ContractTermAdjustments.BaseLengthModifier);
        }

        private short GetSalvageRights(ContractType contractType, Employer employer, Rating rating, 
                                       bool isCovert, out SalvageRightsType salvageType)
        {
            short totalRoll = (short)(Die.Instance.RollDice(2) +
                              _termAdjustments.GetSalvageModifier(contractType, employer, rating, isCovert) +
                              _era.ContractAdjustments.ContractTermAdjustments.SalvageModifier);
            if (totalRoll < 2)
            {
                salvageType = SalvageRightsType.None;
                return 0;
            }
            if (totalRoll < 4)
            {
                salvageType = SalvageRightsType.Exchange;
                short secondRoll, percent;
                do
                {
                    secondRoll = (short)(Die.Instance.RollDice(2) +
                                        _termAdjustments.GetSalvageModifier(contractType, employer, rating, isCovert) +
                                        _era.ContractAdjustments.ContractTermAdjustments.SalvageModifier);
                    percent = GetSalvagePercentage(secondRoll);
                }
                while (percent == 0);
                return percent;
            }
            if (totalRoll > 12)
            {
                salvageType = SalvageRightsType.Full;
                return 100;
            }
            else
            {
                salvageType = SalvageRightsType.Shared;
                return GetSalvagePercentage(totalRoll);
            }
        }

        private short GetSalvagePercentage(short dieRoll)
        {
            if (dieRoll < 4)
            {
                return 0;
            }
            if (dieRoll > 12)
            {
                return 0;
            }
            else
            {
                return (short)(10 * (dieRoll - 3));
            }
        }

        private void GetSupportRights(ContractType contractType, Employer employer, Rating rating,
                                      bool isCovert, out float straightSupport, out float battleSupport)
        {
            byte supportTypeRoll = Die.Instance.RollDie();
            short totalRoll = (short)(Die.Instance.RollDice(2) +
                              _termAdjustments.GetSupportModifier(contractType, employer, rating, isCovert) +
                              _era.ContractAdjustments.ContractTermAdjustments.SupportModifier);
            if (totalRoll < 3)
            {
                straightSupport = 0;
                battleSupport = 0;
            }
            else if (totalRoll < 13)
            {
                battleSupport = (totalRoll - 1) * 0.05F;
                if (totalRoll < 7)
                {
                    straightSupport = (totalRoll - 2) * 0.1F;
                }
                else
                {
                    straightSupport = (totalRoll - 3) * 0.1F;
                }
            }
            else
            {
                straightSupport = 1;
                battleSupport = 0.6F;
            }
            
            if (supportTypeRoll < 4)
            {
                battleSupport = 0;
            }
            else if (supportTypeRoll < 6)
            {
                straightSupport = 0;
            }
        }

        private void GetTransportRights(ContractType contractType, Employer employer, Rating rating,
                                        bool isCovert, out float chartered, out float owned)
        {
            short totalRoll = (short)(Die.Instance.RollDice(2) +
                              _termAdjustments.GetTransportModifier(contractType, employer, rating, isCovert) +
                              _era.ContractAdjustments.ContractTermAdjustments.TransportModifier);
            if (totalRoll < 2)
            {
                chartered = 0;
                owned = 0;
            }
            else if (totalRoll < 9)
            {
                if (totalRoll < 7)
                {
                    owned = (short)((totalRoll - 1) * 0.1);
                }
                else
                {
                    owned = (short)((totalRoll * 0.05) +  0.2);
                }
                chartered = (short)((totalRoll * 0.05) + 0.15);
            }
            else
            {
                owned = 1;
                chartered = 1;
            }
        }

        private CommandRights GetCommandRights(ContractType contractType, Employer employer, Rating rating, bool isCovert)
        {
            short totalRoll = (short)(Die.Instance.RollDice(2) +
                                      _termAdjustments.GetCommandModifier(contractType, employer, rating, isCovert) +
                                      _era.ContractAdjustments.ContractTermAdjustments.CommandModifier);
            if (totalRoll < 3)
            {
                return CommandRights.Integrated;
            }
            if (totalRoll < 8)
            {
                return CommandRights.House;
            }
            if (totalRoll < 12)
            {
                return CommandRights.Liason;
            }
            else
            {
                return CommandRights.Independent;
            }
        }

        private float GetOverheadCompensation(ContractType contractType, Employer employer, Rating rating, bool isCovert)
        {
            short totalRoll = (short)(Die.Instance.RollDice(2) +
                                     _termAdjustments.GetOverheadModifier(contractType, employer, rating, isCovert) +
                                     _era.ContractAdjustments.ContractTermAdjustments.OverheadModifier);
            if (totalRoll < 3)
            {
                return 0F;
            }
            if (totalRoll < 7)
            {
                return 0.5F;
            }
            else
            {
                return 1F;
            }
        }
        #endregion
    }
}
